不知不覺目前已經完賽兩週了,太感人了!
今天要講的是macro
跟Excel VBA那個macro 使用巨集錄製器自動化工作
是不一樣的喔!(no no no搖手指)
不負責任翻譯:macro!? 是什麼東西? (嚇到吃手手)
之前在第三天提到,Clojure程式是由reader處理。
所謂的reader會一次讀取一個form,再算出form代表的東西。
例如以下算式的 *
,+
Ref:
https://livebook.manning.com/concept/clojure/form
另外一方面,
macro的作用就是可以用來調整form,先對form動手腳(調整data structure),然後才回去求值
這樣的好處是可以幫我們提供一些語法上的抽象化
找資料時很感謝網路上的大大畫了這張流程圖(Reference):
那我們趕快一邊來參考Clojure Threading Macros Guide,一邊定義自己的macro喔!
https://clojure.org/guides/threading_macros
我們來定義一個macro可以把參數做轉換為字串
並回傳:
(defmacro simple-str [a] (str a))
=> #'tutorial.core/simple-str
這就是所謂的 對form動手腳
(simple-str [1 2 3 4])
=>"[1 2 3 4]"
(simple-str (1 2 3 4))
"(1 2 3 4)"
(simple-str (+ 1 2 +3 +4))
=> "(+ 1 2 3 4)"
那我們不加工,保持原味最對味(想直接拿到原本form的metadata.),該怎麼做呢?
可以使用 &form
把 &args
印出
(defmacro print-form [& args] ;
(println &form))
用一些簡單的小例子來試試:
(print-form 123 :abc ["x" "y" "z"])
=> (print-form 123 :abc [x y z])
nil
(print-form [1 2 3 4 5])
=> (print-form [1 2 3 4 5])
nil
(print-form (+ 1 2))
=> (print-form (+ 1 2))
nil
&form
vs. apply
(defmacro print-form [& args]
(println &form))
其實可以用
(defmacro print-form-with-apply [& args]
(println (apply list 'print-form-with-apply args)))
改寫~
'print-form-with-apply
前面是 Quote (')
的意思,可以得到完完整整的fn name!
(quote form)
=> form
(quote print-form-with-apply)
=> print-form-with-apply
apply
後面先接一個function,以及一到多個參數
(apply f args)(apply f x args)(apply f x y args)(apply f x y z args)
(apply f a b c d & args)
(defmacro print-form-with-apply [& args]
(println (apply list 'print-form-with-apply args)))
=>
#'tutorial.core/print-form-with-apply
(print-form-with-apply (1 2 3))
;; 原本是什麼form我就連名(form)帶姓(fn name)的印出來
=>(print-form-with-apply (1 2 3))
nil
假設我們想要回傳list裡最小的數字,可以拿給Numbers用的min
來實作
舉個例子,接到某個function的回傳值是vector[50 100 150]
(min [50 100 150])
=> [50 100 150]
代表min(只)收到一組vector參數 [50 100 150]
但我們如果要拿vector裡的三個數字來比,加個apply就可以達到效果
(apply min [50 100 150])
;;(apply f a b c d & args)
=> 50
大家對apply有一點感覺了嗎?
apply分別拿出了vector內的element來比較min
然後我們就發現第九天Clojure Functional Programming與資料操作(2) - reduce
簡單的
(reduce + (range 1 101))`
使用情境,
其實是可以用
(apply + (range 1 101))
來取代。
但去研究apply vs. reduce又會開啟另一個黑洞,還是先不要好了 ~
之所以會順手提到apply,是因為我們發現他跟
thread-first macro (->) vs thread-last macro (->>)
擺的位置分類實在是很接近,
所以就順便介紹新成員出場,讓大家增廣見聞一下~
明天就要正式進入到好多箭頭
系列啦!敬請期待:)
as->
cond->
cond->>
some->
some->>
defmacro
定義 unless第11天在講Flow Control
提到if和if-not時,曾經聊到其他語言if-not有的有 unless
語法
(if-not (empty? []) :true :false)
=> :false
這種舉反例的想法是工程師的最愛(?)
趕快來用defmacro
實做一個自己的unless
(defmacro unless [pred statement-a statement-b]
`(if (not ~pred) ~statement-a ~statement-b))
前面的毛毛蟲~(Unquote)
剛好跟提到的 '(Quote)
相反,會把表達式變成值
Within a template, form will be treated as an expression to be replaced by its value.
定義好unless就來用用看囉!
(if-not (empty? []) :true :false)
=> :false
(unless (empty? []) :true :false)
=> :false
(unless false (println "OK Will do") (println "false is false"))
=> OK Will do
nil
(unless (not= 1 1) (println "1 + 1 equals 2") (println "false is false"))
1 + 1 equals 2
nil
macroexpand
macro是可以透過macroexpand
展開成原本的形式
例如
when是(if .. do ..)的macro(巨集),可以用 macroexpand來展開看原本的樣子:
(macroexpand '(when (empty? []) true))
=>(if (empty? []) (do true))
->
來破梗一下明天會介紹的單箭頭
(macroexpand '(-> 1 (+ 2) (* 3)))
=> (* (+ 1 2) 3)
(-> 1 (+ 2) (* 3))
這樣運算式的順序是不是對人類眼睛比較好讀呢?(先加二再乘以三)
macroexpand-1
macroexpand-1
會去判斷是否macro form,是的話回傳展開始,否則回傳normal form
If form represents a macro form, returns its expansion,
else returns form.
用上面的(先加二再乘以三)舉個例子,macroexpand-1
後接 'single quote
;; macro form return its expansion
(macroexpand-1 '(-> 1 (+ 2) (* 3)))
(* (+ 1 2) 3)
;; form is not macro form, returns form
(macroexpand-1 '(* (+ 1 2) 3))
(* (+ 1 2) 3)
咦?
那跟同場加映2的macroexpand
的結果有什麼不同呢?
(macroexpand '(-> 1 (+ 2) (* 3)))
=> (* (+ 1 2) 3)
其實沒有-1
的,macroexpand
就是macroexpand-1
重複做很多次的意思啦~
Repeatedly calls macroexpand-1 on form
until it no longer represents a macro form, then returns it.
macroexpand-1
Syntax-quote最後,稍微提一下
還有另一個東東叫做Syntax-quote
,或是backquote
是給symbols和四大data collection(lists/ vectors/ sets/ maps) 之外用的
放一個clojure文件上的例子example reference:
; When testing macro expansion in a file instead of at the REPL,
; please note that it may be necessary to use a backquote
; instead of a straight quote.
(defmacro iiinc [x]
`(+ 3 ~x))
(deftest t-stuff
; This doesn't work.
(println (macroexpand-1 '(iiinc 2))) ;=> (iiinc 2)
; Oddly, we can use the macro itself fine in our tests...
(println (iiinc 2)) ;=> 5
(is (= 5 (iiinc 2))) ;=> unit test passes
; This fixes it by resolving the symbol iiinc at compile-time.
(println (macroexpand-1 `(iiinc 2)))) ;=> (+ 3 2)
; Also, as the previous examples show, please remember that
; you must quote the form you are providing to `macroexpand-1`.
macro的世界太多種quote了是否覺得心累呢?
來句Motivational Quotes
激勵自己吧!
工程師的千里之行,始於足下:)